using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Collections;

namespace Norm.Helper
{
    public sealed class ObjHelper
    {
        /// <summary>
        /// Clones an object of Type A to object of Type B. 
        /// Gets around namespace issues caused by Web References
        /// </summary>
        /// <param name="oin"></param>
        /// <param name="oout"></param>
        /// <returns></returns>
        public static object Clone(object oin, object oout)
        {
            foreach (FieldInfo curField in oout.GetType().GetFields())
            {
                FieldInfo fin = oin.GetType().GetField(curField.Name);
                if (fin == null)
                    continue;

                curField.SetValue(oout, fin.GetValue(oin));
            }
            return oout;
        }

        /// <summary>
        /// Returns true if the member can be set
        /// </summary>
        /// <remarks>If not a property/field it will return false</remarks>
        /// <param name="mi"></param>
        /// <returns></returns>
        public static bool IsWritableMember(MemberInfo mi)
        {
            switch (mi.MemberType)
            {
                case MemberTypes.Property:
                    return ((PropertyInfo)mi).CanWrite;
                case MemberTypes.Field:
                    return true;
                default: return false;
            }
        }


        /// <summary>
        /// Set the property/field of object o to value v
        /// </summary>
        /// <remarks>
        /// If multiple members are found first writable one is returned
        /// </remarks>
        /// <param name="o"></param>
        /// <param name="v"></param>
        /// <param name="memberName"></param>
        public static void SetMember(object o, object v, string memberName)
        { 
            MemberInfo[] mis =  o.GetType().GetMember(memberName);
            foreach(MemberInfo mi in mis)
                if (IsWritableMember(mi))
                {
                    SetMember(o, v, mi);
                    return;
                }
        }
        public static void SetMember(object o, object v,MemberInfo mi)
        {
            
            switch (mi.MemberType)
            {               
                case MemberTypes.Property:
                    PropertyInfo pi = (PropertyInfo)mi;
                    if (!pi.CanWrite)
                        throw new Exception.UnableToWrite();
                    pi.SetValue(o, v, null);
                    return;

                case MemberTypes.Field:
                    FieldInfo fi = (FieldInfo)mi;
                    fi.SetValue(o, v);
                    return;
            
            }
        }
        /// <summary>
        /// Get the value from the member
        /// </summary>
        /// <param name="o"></param>
        /// <param name="mi"></param>
        /// <returns></returns>
        public static object GetMemberValue(object o, MemberInfo mi)
        {
            switch (mi.MemberType)
            {
                case MemberTypes.Field:
                    return ((FieldInfo)mi).GetValue(o);
                case MemberTypes.Property:
                    return ((PropertyInfo)mi).GetValue(o, null);
                default: throw new Exception.NotValidEnumValue();
            }
        }

        /// <summary>
        /// Returns the type held in the member
        /// </summary>
        /// <param name="mi"></param>
        /// <returns></returns>
        public static Type TypeOfMember(MemberInfo mi)
        {
            PropertyInfo pi = mi.GetType().GetProperty(mi.MemberType + "Type");
            if (pi == null)
                throw new Exception.InvalidName();
            return (Type)pi.GetValue(ObjHelper.NewObj(pi.DeclaringType), null);
        }

        /// <summary>
        /// Get an instance of the type in the array ie:
        /// foo[] => new foo()
        /// </summary>
        /// <param name="fi"></param>
        /// <returns>
        /// Slight hack where we reflect the name, 
        /// drop the [] and create what ever that evals to</returns>
        public static object InstanceOfArrayType(MemberInfo fi)
        {
            return InstanceOfArrayType(TypeOfMember(fi));
        }
        public static object InstanceOfArrayType(Type t)
        {
            string typeName = t.FullName.TrimEnd("[]".ToCharArray());
            return t.Assembly.CreateInstance(typeName);
        }

        public static Hashtable ToHash(object o)
        {
            Hashtable hash = new Hashtable();
            if (o == null) return hash;

            foreach (FieldInfo field in o.GetType().GetFields())
                hash[field.Name] = field.GetValue(o);
            return hash;
        }

        public static object[] FieldAttributes(string field, object o, Type attType)
        {
            FieldInfo f = o.GetType().GetField(field);
            return FieldAttributes(f, attType);
        }

        public static object[] FieldAttributes(FieldInfo f, Type attType)
        {
            if (f == null) throw new Exception.InvalidName();
            return f.GetCustomAttributes(attType, true);
        }

        public static object[] MemberAttributes(MemberInfo mi, Type attType)
        {
            if (mi == null) throw new Exception.InvalidName();
            return mi.GetCustomAttributes(attType, true);
        }

        public static object[] ClassAttributes(object o, Type attType)
        {
            return o.GetType().GetCustomAttributes(attType, true);
        }

        public static object NewObj(Type t)
        { return Activator.CreateInstance(t); }
    }
}
